home *** CD-ROM | disk | FTP | other *** search
/ Languguage OS 2 / Languguage OS II Version 10-94 (Knowledge Media)(1994).ISO / gnu / dejagnu.lha / dejagnu-1.0.1 / tcl / tclGlob.c < prev    next >
C/C++ Source or Header  |  1993-02-14  |  15KB  |  560 lines

  1. /* 
  2.  * tclGlob.c --
  3.  *
  4.  *    This file provides procedures and commands for file name
  5.  *    manipulation, such as tilde expansion and globbing.
  6.  *
  7.  * Copyright 1990-1991 Regents of the University of California
  8.  * Permission to use, copy, modify, and distribute this
  9.  * software and its documentation for any purpose and without
  10.  * fee is hereby granted, provided that the above copyright
  11.  * notice appear in all copies.  The University of California
  12.  * makes no representations about the suitability of this
  13.  * software for any purpose.  It is provided "as is" without
  14.  * express or implied warranty.
  15.  */
  16.  
  17. #include "tclInt.h"
  18. #include "tclUnix.h"
  19.  
  20. /*
  21.  * The structure below is used to keep track of a globbing result
  22.  * being built up (i.e. a partial list of file names).  The list
  23.  * grows dynamically to be as big as needed.
  24.  */
  25.  
  26. typedef struct {
  27.     char *result;        /* Pointer to result area. */
  28.     int totalSpace;        /* Total number of characters allocated
  29.                  * for result. */
  30.     int spaceUsed;        /* Number of characters currently in use
  31.                  * to hold the partial result (not including
  32.                  * the terminating NULL). */
  33.     int dynamic;        /* 0 means result is static space, 1 means
  34.                  * it's dynamic. */
  35. } GlobResult;
  36.  
  37. /*
  38.  * Declarations for procedures local to this file:
  39.  */
  40.  
  41. static void        AppendResult _ANSI_ARGS_((Tcl_Interp *interp,
  42.                 char *dir, char *separator, char *name,
  43.                 int nameLength));
  44. static int        DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
  45.                 char *rem));
  46.  
  47. /*
  48.  *----------------------------------------------------------------------
  49.  *
  50.  * AppendResult --
  51.  *
  52.  *    Given two parts of a file name (directory and element within
  53.  *    directory), concatenate the two together and append them to
  54.  *    the result building up in interp.
  55.  *
  56.  * Results:
  57.  *    There is no return value.
  58.  *
  59.  * Side effects:
  60.  *    Interp->result gets extended.
  61.  *
  62.  *----------------------------------------------------------------------
  63.  */
  64.  
  65. static void
  66. AppendResult(interp, dir, separator, name, nameLength)
  67.     Tcl_Interp *interp;        /* Interpreter whose result should be
  68.                  * appended to. */
  69.     char *dir;            /* Name of directory, without trailing
  70.                  * slash except for root directory. */
  71.     char *separator;        /* Separator string so use between dir and
  72.                  * name:  either "/" or "" depending on dir. */
  73.     char *name;            /* Name of file withing directory (NOT
  74.                  * necessarily null-terminated!). */
  75.     int nameLength;        /* Number of characters in name. */
  76. {
  77.     int dirFlags, nameFlags;
  78.     char *p, saved;
  79.  
  80.     /*
  81.      * Next, see if we can put together a valid list element from dir
  82.      * and name by calling Tcl_AppendResult.
  83.      */
  84.  
  85.     if (*dir == 0) {
  86.     dirFlags = 0;
  87.     } else {
  88.     Tcl_ScanElement(dir, &dirFlags);
  89.     }
  90.     saved = name[nameLength];
  91.     name[nameLength] = 0;
  92.     Tcl_ScanElement(name, &nameFlags);
  93.     if ((dirFlags == 0) && (nameFlags == 0)) {
  94.     if (*interp->result != 0) {
  95.         Tcl_AppendResult(interp, " ", dir, separator, name, (char *) NULL);
  96.     } else {
  97.         Tcl_AppendResult(interp, dir, separator, name, (char *) NULL);
  98.     }
  99.     name[nameLength] = saved;
  100.     return;
  101.     }
  102.  
  103.     /*
  104.      * This name has weird characters in it, so we have to convert it to
  105.      * a list element.  To do that, we have to merge the characters
  106.      * into a single name.  To do that, malloc a buffer to hold everything.
  107.      */
  108.  
  109.     p = (char *) ckalloc((unsigned) (strlen(dir) + strlen(separator)
  110.         + nameLength + 1));
  111.     sprintf(p, "%s%s%s", dir, separator, name);
  112.     name[nameLength] = saved;
  113.     Tcl_AppendElement(interp, p, 0);
  114.     ckfree(p);
  115. }
  116.  
  117. /*
  118.  *----------------------------------------------------------------------
  119.  *
  120.  * DoGlob --
  121.  *
  122.  *    This recursive procedure forms the heart of the globbing
  123.  *    code.  It performs a depth-first traversal of the tree
  124.  *    given by the path name to be globbed.
  125.  *
  126.  * Results:
  127.  *    The return value is a standard Tcl result indicating whether
  128.  *    an error occurred in globbing.  After a normal return the
  129.  *    result in interp will be set to hold all of the file names
  130.  *    given by the dir and rem arguments.  After an error the
  131.  *    result in interp will hold an error message.
  132.  *
  133.  * Side effects:
  134.  *    None.
  135.  *
  136.  *----------------------------------------------------------------------
  137.  */
  138.  
  139. static int
  140. DoGlob(interp, dir, rem)
  141.     Tcl_Interp *interp;            /* Interpreter to use for error
  142.                      * reporting (e.g. unmatched brace). */
  143.     char *dir;                /* Name of a directory at which to
  144.                      * start glob expansion.  This name
  145.                      * is fixed: it doesn't contain any
  146.                      * globbing chars. */
  147.     char *rem;                /* Path to glob-expand. */
  148. {
  149.     /*
  150.      * When this procedure is entered, the name to be globbed may
  151.      * already have been partly expanded by ancestor invocations of
  152.      * DoGlob.  The part that's already been expanded is in "dir"
  153.      * (this may initially be empty), and the part still to expand
  154.      * is in "rem".  This procedure expands "rem" one level, making
  155.      * recursive calls to itself if there's still more stuff left
  156.      * in the remainder.
  157.      */
  158.  
  159.     register char *p;
  160.     register char c;
  161.     char *openBrace, *closeBrace;
  162.     int gotSpecial, result;
  163.     char *separator;
  164.  
  165.     /*
  166.      * Figure out whether we'll need to add a slash between the directory
  167.      * name and file names within the directory when concatenating them
  168.      * together.
  169.      */
  170.  
  171.     if ((dir[0] == 0) || ((dir[0] == '/') && (dir[1] == 0))) {
  172.     separator = "";
  173.     } else {
  174.     separator = "/";
  175.     }
  176.  
  177.     /*
  178.      * When generating information for the next lower call,
  179.      * use static areas if the name is short, and malloc if the name
  180.      * is longer.
  181.      */
  182.  
  183. #define STATIC_SIZE 200
  184.  
  185.     /*
  186.      * First, find the end of the next element in rem, checking
  187.      * along the way for special globbing characters.
  188.      */
  189.  
  190.     gotSpecial = 0;
  191.     openBrace = closeBrace = NULL;
  192.     for (p = rem; ; p++) {
  193.     c = *p;
  194.     if ((c == '\0') || (c == '/')) {
  195.         break;
  196.     }
  197.     if ((c == '{') && (openBrace == NULL)) {
  198.         openBrace = p;
  199.     }
  200.     if ((c == '}') && (closeBrace == NULL)) {
  201.         closeBrace = p;
  202.     }
  203.     if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
  204.         gotSpecial = 1;
  205.     }
  206.     }
  207.  
  208.     /*
  209.      * If there is an open brace in the argument, then make a recursive
  210.      * call for each element between the braces.  In this case, the
  211.      * recursive call to DoGlob uses the same "dir" that we got.
  212.      * If there are several brace-pairs in a single name, we just handle
  213.      * one here, and the others will be handled in recursive calls.
  214.      */
  215.  
  216.     if (openBrace != NULL) {
  217.     int remLength, l1, l2;
  218.     char static1[STATIC_SIZE];
  219.     char *element, *newRem;
  220.  
  221.     if (closeBrace == NULL) {
  222.         Tcl_ResetResult(interp);
  223.         interp->result = "unmatched open-brace in file name";
  224.         return TCL_ERROR;
  225.     }
  226.     remLength = strlen(rem) + 1;
  227.     if (remLength <= STATIC_SIZE) {
  228.         newRem = static1;
  229.     } else {
  230.         newRem = (char *) ckalloc((unsigned) remLength);
  231.     }
  232.     l1 = openBrace-rem;
  233.     strncpy(newRem, rem, l1);
  234.     p = openBrace;
  235.     for (p = openBrace; *p != '}'; ) {
  236.         element = p+1;
  237.         for (p = element; ((*p != '}') && (*p != ',')); p++) {
  238.         /* Empty loop body:  just find end of this element. */
  239.         }
  240.         l2 = p - element;
  241.         strncpy(newRem+l1, element, l2);
  242.         strcpy(newRem+l1+l2, closeBrace+1);
  243.         if (DoGlob(interp, dir, newRem) != TCL_OK) {
  244.         return TCL_ERROR;
  245.         }
  246.     }
  247.     if (remLength > STATIC_SIZE) {
  248.         ckfree(newRem);
  249.     }
  250.     return TCL_OK;
  251.     }
  252.  
  253.     /*
  254.      * If there were any pattern-matching characters, then scan through
  255.      * the directory to find all the matching names.
  256.      */
  257.  
  258.     if (gotSpecial) {
  259.     DIR *d;
  260.     struct dirent *entryPtr;
  261.     int l1, l2;
  262.     char *pattern, *newDir, *dirName;
  263.     char static1[STATIC_SIZE], static2[STATIC_SIZE];
  264.     struct stat statBuf;
  265.  
  266.     /*
  267.      * Be careful not to do any actual file system operations on a
  268.      * directory named "";  instead, use ".".  This is needed because
  269.      * some versions of UNIX don't treat "" like "." automatically.
  270.      */
  271.  
  272.     if (*dir == '\0') {
  273.         dirName = ".";
  274.     } else {
  275.         dirName = dir;
  276.     }
  277.     if ((stat(dirName, &statBuf) != 0) || !S_ISDIR(statBuf.st_mode)) {
  278.         return TCL_OK;
  279.     }
  280.     d = opendir(dirName);
  281.     if (d == NULL) {
  282.         Tcl_ResetResult(interp);
  283.         Tcl_AppendResult(interp, "couldn't read directory \"",
  284.             dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
  285.         return TCL_ERROR;
  286.     }
  287.     l1 = strlen(dir);
  288.     l2 = (p - rem);
  289.     if (l2 < STATIC_SIZE) {
  290.         pattern = static2;
  291.     } else {
  292.         pattern = (char *) ckalloc((unsigned) (l2+1));
  293.     }
  294.     strncpy(pattern, rem, l2);
  295.     pattern[l2] = '\0';
  296.     result = TCL_OK;
  297.     while (1) {
  298.         entryPtr = readdir(d);
  299.         if (entryPtr == NULL) {
  300.         break;
  301.         }
  302.  
  303.         /*
  304.          * Don't match names starting with "." unless the "." is
  305.          * present in the pattern.
  306.          */
  307.  
  308.         if ((*entryPtr->d_name == '.') && (*pattern != '.')) {
  309.         continue;
  310.         }
  311.         if (Tcl_StringMatch(entryPtr->d_name, pattern)) {
  312.         int nameLength = strlen(entryPtr->d_name);
  313.         if (*p == 0) {
  314.             AppendResult(interp, dir, separator, entryPtr->d_name,
  315.                 nameLength);
  316.         } else {
  317.             if ((l1+nameLength+2) <= STATIC_SIZE) {
  318.             newDir = static1;
  319.             } else {
  320.             newDir = (char *) ckalloc((unsigned) (l1+nameLength+2));
  321.             }
  322.             sprintf(newDir, "%s%s%s", dir, separator, entryPtr->d_name);
  323.             result = DoGlob(interp, newDir, p+1);
  324.             if (newDir != static1) {
  325.             ckfree(newDir);
  326.             }
  327.             if (result != TCL_OK) {
  328.             break;
  329.             }
  330.         }
  331.         }
  332.     }
  333.     closedir(d);
  334.     if (pattern != static2) {
  335.         ckfree(pattern);
  336.     }
  337.     return result;
  338.     }
  339.  
  340.     /*
  341.      * This is the simplest case:  just another path element.  Move
  342.      * it to the dir side and recurse (or just add the name to the
  343.      * list, if we're at the end of the path).
  344.      */
  345.  
  346.     if (*p == 0) {
  347.     AppendResult(interp, dir, separator, rem, p-rem);
  348.     } else {
  349.     int l1, l2;
  350.     char *newDir;
  351.     char static1[STATIC_SIZE];
  352.  
  353.     l1 = strlen(dir);
  354.     l2 = l1 + (p - rem) + 2;
  355.     if (l2 <= STATIC_SIZE) {
  356.         newDir = static1;
  357.     } else {
  358.         newDir = (char *) ckalloc((unsigned) l2);
  359.     }
  360.     sprintf(newDir, "%s%s%.*s", dir, separator, p-rem, rem);
  361.     result = DoGlob(interp, newDir, p+1);
  362.     if (newDir != static1) {
  363.         ckfree(newDir);
  364.     }
  365.     if (result != TCL_OK) {
  366.         return TCL_ERROR;
  367.     }
  368.     }
  369.     return TCL_OK;
  370. }
  371.  
  372. /*
  373.  *----------------------------------------------------------------------
  374.  *
  375.  * Tcl_TildeSubst --
  376.  *
  377.  *    Given a name starting with a tilde, produce a name where
  378.  *    the tilde and following characters have been replaced by
  379.  *    the home directory location for the named user.
  380.  *
  381.  * Results:
  382.  *    The result is a pointer to a static string containing
  383.  *    the new name.  This name will only persist until the next
  384.  *    call to Tcl_TildeSubst;  save it if you care about it for
  385.  *    the long term.  If there was an error in processing the
  386.  *    tilde, then an error message is left in interp->result
  387.  *    and the return value is NULL.
  388.  *
  389.  * Side effects:
  390.  *    None that the caller needs to worry about.
  391.  *
  392.  *----------------------------------------------------------------------
  393.  */
  394.  
  395. char *
  396. Tcl_TildeSubst(interp, name)
  397.     Tcl_Interp *interp;        /* Interpreter in which to store error
  398.                  * message (if necessary). */
  399.     char *name;            /* File name, which may begin with "~/"
  400.                  * (to indicate current user's home directory)
  401.                  * or "~<user>/" (to indicate any user's
  402.                  * home directory). */
  403. {
  404. #define STATIC_BUF_SIZE 50
  405.     static char staticBuf[STATIC_BUF_SIZE];
  406.     static int curSize = STATIC_BUF_SIZE;
  407.     static char *curBuf = staticBuf;
  408.     char *dir;
  409.     int length;
  410.     int fromPw = 0;
  411.     register char *p;
  412.  
  413.     if (name[0] != '~') {
  414.     return name;
  415.     }
  416.  
  417.     /*
  418.      * First, find the directory name corresponding to the tilde entry.
  419.      */
  420.  
  421.     if ((name[1] == '/') || (name[1] == '\0')) {
  422.     dir = getenv("HOME");
  423.     if (dir == NULL) {
  424.         Tcl_ResetResult(interp);
  425.         Tcl_AppendResult(interp, "couldn't find HOME environment ",
  426.             "variable to expand \"", name, "\"", (char *) NULL);
  427.         return NULL;
  428.     }
  429.     p = name+1;
  430.     } else {
  431.     struct passwd *pwPtr;
  432.  
  433.     for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
  434.         /* Null body;  just find end of name. */
  435.     }
  436.     length = p-&name[1];
  437.     if (length >= curSize) {
  438.         length = curSize-1;
  439.     }
  440.     memcpy((VOID *) curBuf, (VOID *) (name+1), length);
  441.     curBuf[length] = '\0';
  442.     pwPtr = getpwnam(curBuf);
  443.     if (pwPtr == NULL) {
  444.         endpwent();
  445.         Tcl_ResetResult(interp);
  446.         Tcl_AppendResult(interp, "user \"", curBuf,
  447.             "\" doesn't exist", (char *) NULL);
  448.         return NULL;
  449.     }
  450.     dir = pwPtr->pw_dir;
  451.     fromPw = 1;
  452.     }
  453.  
  454.     /*
  455.      * Grow the buffer if necessary to make enough space for the
  456.      * full file name.
  457.      */
  458.  
  459.     length = strlen(dir) + strlen(p);
  460.     if (length >= curSize) {
  461.     if (curBuf != staticBuf) {
  462.         ckfree(curBuf);
  463.     }
  464.     curSize = length + 1;
  465.     curBuf = (char *) ckalloc((unsigned) curSize);
  466.     }
  467.  
  468.     /*
  469.      * Finally, concatenate the directory name with the remainder
  470.      * of the path in the buffer.
  471.      */
  472.  
  473.     strcpy(curBuf, dir);
  474.     strcat(curBuf, p);
  475.     if (fromPw) {
  476.     endpwent();
  477.     }
  478.     return curBuf;
  479. }
  480.  
  481. /*
  482.  *----------------------------------------------------------------------
  483.  *
  484.  * Tcl_GlobCmd --
  485.  *
  486.  *    This procedure is invoked to process the "glob" Tcl command.
  487.  *    See the user documentation for details on what it does.
  488.  *
  489.  * Results:
  490.  *    A standard Tcl result.
  491.  *
  492.  * Side effects:
  493.  *    See the user documentation.
  494.  *
  495.  *----------------------------------------------------------------------
  496.  */
  497.  
  498.     /* ARGSUSED */
  499. int
  500. Tcl_GlobCmd(dummy, interp, argc, argv)
  501.     ClientData dummy;            /* Not used. */
  502.     Tcl_Interp *interp;            /* Current interpreter. */
  503.     int argc;                /* Number of arguments. */
  504.     char **argv;            /* Argument strings. */
  505. {
  506.     int i, result, noComplain;
  507.  
  508.     if (argc < 2) {
  509.     notEnoughArgs:
  510.     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  511.         " ?-nocomplain? name ?name ...?\"", (char *) NULL);
  512.     return TCL_ERROR;
  513.     }
  514.     noComplain = 0;
  515.     if ((argv[1][0] == '-') && (strcmp(argv[1], "-nocomplain") == 0)) {
  516.     if (argc < 3) {
  517.         goto notEnoughArgs;
  518.     }
  519.     noComplain = 1;
  520.     }
  521.  
  522.     for (i = 1 + noComplain; i < argc; i++) {
  523.     char *thisName;
  524.  
  525.     /*
  526.      * Do special checks for names starting at the root and for
  527.      * names beginning with ~.  Then let DoGlob do the rest.
  528.      */
  529.  
  530.     thisName = argv[i];
  531.     if (*thisName == '~') {
  532.         thisName = Tcl_TildeSubst(interp, thisName);
  533.         if (thisName == NULL) {
  534.         return TCL_ERROR;
  535.         }
  536.     }
  537.     if (*thisName == '/') {
  538.         result = DoGlob(interp, "/", thisName+1);
  539.     } else {
  540.         result = DoGlob(interp, "", thisName);
  541.     }
  542.     if (result != TCL_OK) {
  543.         return result;
  544.     }
  545.     }
  546.     if ((*interp->result == 0) && !noComplain) {
  547.     char *sep = "";
  548.  
  549.     Tcl_AppendResult(interp, "no files matched glob pattern",
  550.         (argc == 2) ? " \"" : "s \"", (char *) NULL);
  551.     for (i = 1; i < argc; i++) {
  552.         Tcl_AppendResult(interp, sep, argv[i], (char *) NULL);
  553.         sep = " ";
  554.     }
  555.     Tcl_AppendResult(interp, "\"", (char *) NULL);
  556.     return TCL_ERROR;
  557.     }
  558.     return TCL_OK;
  559. }
  560.